Skip to content

Drive section neighbor-finding from xgcm topology metadata#5

Closed
hdrake wants to merge 1 commit into
masterfrom
topology-driven-neighbors
Closed

Drive section neighbor-finding from xgcm topology metadata#5
hdrake wants to merge 1 commit into
masterfrom
topology-driven-neighbors

Conversation

@hdrake

@hdrake hdrake commented Jun 23, 2026

Copy link
Copy Markdown
Owner

What & why

Section-finding currently hard-codes how to deal with a few grid topologies via a topology= string ("latlon"/"cartesian"/"MOM-tripolar"), and the whole transport/tracer pipeline assumes a single logically-rectangular (Y, X) tile. This PR makes neighbor-finding read from the xgcm.Grid the caller already supplies — its boundary and face_connections metadata — so periodicity and inter-tile connectivity "just work," and extends section-finding and transports to multi-tile grids (lat-lon-cap, cubed-sphere).

Changes

Single-tile (no behavior change). Neighbors follow from each axis' boundary ("periodic" → wrap, else clip), read via the public axis.boundary property (drops private ._boundary). The topology= argument and the hard-coded north-fold formula are gone. Existing tests pass unchanged.

Multi-tile geometry (face_connections). gridutils.build_neighbor_maps builds index-valued arrays, pads them once with xgcm, and reads the halos to get each corner's neighbors with axis-rotation/reversal already applied — reusing xgcm's tested padding rather than reimplementing topology. A reciprocity check raises rather than return the silently-wrong neighbors xgcm's padding produces (hash-seed-dependently) for the hardest reversed topologies. Section state carries a face index; grid_section returns it for multi-tile grids.

Transports / tracers across seams. Each section edge's normal velocity is read in a single face's frame (source-first dual-anchor), so no velocity is rotated across a seam. The per-edge sign is computed geometrically — "does the stored velocity point to the left of the section's direction of travel?" — so it stays consistent across rotated faces without relying on a single global orientation factor. convergent_transport/extract_tracer select the contributing face pointwise.

Verification

  • All existing single-tile tests pass unchanged (the geographic sign reproduces them to machine precision).
  • Split-grid equivalence (a 2-face grid is a flat grid cut in half; transport must match the uncut grid) for symmetric and non-symmetric axis-preserving seams, exact to rtol 1e-12.
  • Streamfunction-difference test for a genuinely rotated 2-face grid: transport across the rotated seam = psi(P2) - psi(P1) — a known, non-zero, rotation-independent answer needing no model data.
  • 24 tests pass, deterministic across PYTHONHASHSEED. New tests in sectionate/tests/test_section_multitile.py.

Known limitations (guarded, not silent)

  • The tripolar/bipolar fold (a face self-connection) raises NotImplementedError; xgcm doesn't support it either (xgcm#194).
  • On the most complex grids (full cubed-sphere, lat-lon-cap arctic cap), xgcm's own padding is unreliable, so the reciprocity check can refuse the section path itself — an upstream issue, surfaced as a clear error rather than wrong output.

🤖 Generated with Claude Code

Replace the hard-coded `topology=` string ("latlon"/"cartesian"/"MOM-tripolar")
with neighbor-finding inferred from the `xgcm.Grid` the caller already supplies,
and extend section-finding and transports to multi-tile grids (face_connections).

Single-tile grids: neighbors follow from each axis' `boundary` metadata
("periodic" -> wrap, else clip), read via the public `axis.boundary` property
(no more private `._boundary`). Behavior is unchanged; existing tests pass as-is.

Multi-tile grids (lat-lon-cap, cubed-sphere): `gridutils.build_neighbor_maps`
pads index-valued arrays with xgcm and reads the halos to get each corner's
neighbors with axis-rotation/reversal already applied, reusing xgcm's tested
padding. A reciprocity check raises instead of returning the silently-wrong
neighbors xgcm's padding produces (hash-seed-dependently) for the most complex
reversed topologies. Section state carries a `face` index; `grid_section`
returns the face component for multi-tile grids.

Transports/tracers across face seams: each section edge's normal velocity is
read in a single face's frame (source-first dual-anchor), so nothing is rotated
across the seam, and the per-edge sign is computed geometrically ("does the
velocity point left of travel?") so it stays consistent across rotated faces
without a single global orientation factor. Verified by split-grid equivalence
(a 2-face grid is a flat grid cut in half) for symmetric and non-symmetric
axis-preserving seams, and by a streamfunction-difference test (transport
P1->P2 = psi(P2)-psi(P1)) for a genuinely rotated 2-face grid.

The tripolar/bipolar fold (a face self-connection) is detected and rejected
with NotImplementedError; xgcm does not support it either (xgcm issue #194).

New tests in sectionate/tests/test_section_multitile.py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@hdrake

hdrake commented Jun 23, 2026

Copy link
Copy Markdown
Owner Author

Reopening against MOM6-community/sectionate instead.

@hdrake hdrake closed this Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant